#pragma once
#include <fstream>
#include <sstream>
#include <exception>
#include <vector>
#include <stdexcept>
#include <string>
#include <map>
#include <iostream>
#include <clUtil.h>
#include <algorithm>
#include <cleavage.h>

struct DatabaseEntry
{
  unsigned int xCount;
  std::string comment;
  std::string name;
  std::string sequence;
  
  DatabaseEntry() : 
    xCount(0),
    comment(""),
    name(""),
    sequence("")
  {
  }
};

static const unsigned int kMassOxygen = 18001;

struct Peptide
{
  public:
    unsigned int mMass;
    std::string mAcids;

    Peptide(std::string acids) : mAcids(acids)
    {
      mMass = kMassOxygen;

      //Compute the mass of the peptide
      for(unsigned int i = 0; i < mAcids.size(); i++)
      {
        mMass += getAcidMonoMass(mAcids[i]);
      }
    }

    bool operator<(const Peptide& b) const {return mMass < b.mMass;}
    bool operator<=(const Peptide& b) const {return mMass <= b.mMass;}
    bool operator>(const Peptide& b) const {return mMass > b.mMass;}
    bool operator>=(const Peptide& b) const {return mMass >= b.mMass;}
    bool operator==(const Peptide& b) const {return mMass == b.mMass;}
    bool operator!=(const Peptide& b) const {return mMass != b.mMass;}

  protected:
    Peptide();

    //Return Monoisotopic mass in milliDaltons
    unsigned int getAcidMonoMass(unsigned char acid)
    {
      switch(acid)
      {
        case 'A':
          return 71037;
        case 'C':
          return 103009;
        case 'D':
          return 115027;
        case 'E':
          return 129043;
        case 'F':
          return 147068;
        case 'G':
          return 57021;
        case 'H':
          return 137059;
        case 'I':
          return 113084;
        case 'K':
          return 128095;
        case 'L':
          return 113084;
        case 'M':
          return 131040;
        case 'N':
          return 114043;
        case 'P':
          return 97053;
        case 'Q':
          return 128059;
        case 'R':
          return 156101;
        case 'S':
          return 87032;
        case 'T':
          return 101048;
        case 'V':
          return 99068;
        case 'W':
          return 186079;
        case 'Y':
          return 163063;
        default:
          return 0;
      }
    }

    unsigned int getAcidAvgMass(unsigned char acid)
    {
      return 0;
    }

};

class Database
{
  private:
    std::vector<Peptide> mAllPeptides;
    std::vector<DatabaseEntry> mAllProteins;
    std::multimap<std::string,std::string> mPeptideMap;
    bool (*mCleavageFunction)(char, char);
    std::unique_ptr<char[]> mPeptideDatabase;
    std::unique_ptr<unsigned int[]> mPeptideMasses;
    size_t mNumPeptides;
    bool processEntry(DatabaseEntry& curEntry, unsigned int recursionLevel = 0);
    std::vector<std::unique_ptr<clUtil::Memory>> mDevicePeptideDatabase;
    std::vector<std::unique_ptr<clUtil::Memory>> mDevicePeptideMasses;
    unsigned int mCurProtein;

  public:
    Database(const char* filename, bool (*cleavageFunction)(char, char));
    bool copyToDevices();
    void printPeptides();
    unsigned int getNumPeptides(){return mNumPeptides;}
    clUtil::Memory* getPeptides() const 
    {
      return mDevicePeptideDatabase[clUtil::Device::GetCurrentDeviceNum()].
        get();
    }
    clUtil::Memory* getMasses()
    {
      return mDevicePeptideMasses[clUtil::Device::GetCurrentDeviceNum()].get();
    }
    Peptide& getPeptide(unsigned int num);
    std::pair<std::multimap<std::string, std::string>::iterator, 
              std::multimap<std::string, std::string>::iterator> 
      getProtein(std::string& peptide);
};
